#pragma once
#include <iostream>
using namespace std;
#include <queue>
#include <pthread.h>
#include <string>
#include <random>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>

#define CAP_INIT 8

template <class T>
class BlockQueue
{
public:
    // 生产
    void Push(const T &data)
    {
        // 临界区，开始挂锁
        LockQueue();
        // if(IsFull)?bug
        // 此处使用while而不是if，保证是因为队列满了生产者而被唤醒
        // 避免伪唤醒，和挂起失败情况
        while (IsFull())
        {
            // 唤醒消费者消费
            WakeupConsumer();
            ProducterWait();
        }
        _bq.push(data);
        // 取消锁
        UnlockQueue();
    }

    // 消费
    void Pop(T *data)
    {
        // 临界区，开始挂锁
        LockQueue();
        while (IsEmpty())
        {
            ConsumerWait();
        }
        *data = _bq.front();
        _bq.pop();
        // 消费完成，取消锁
        UnlockQueue();
        // 唤醒生产者生产
        if (_bq.size() < _cap / 2)
            WakeupProducter();
    }

private:
    bool IsFull()
    {
        return _bq.size() == _cap;
    }

    bool IsEmpty()
    {
        return _bq.size() == 0;
    }

    // 封装挂锁函数
    void LockQueue()
    {
        pthread_mutex_lock(&mtx);
    }
    // 封装取消锁函数
    void UnlockQueue()
    {
        pthread_mutex_unlock(&mtx);
    }

    void ProducterWait()
    {
        // 挂起等待时，会自动释放锁再挂起
        // 被唤醒之后会自动竞争锁
        pthread_cond_wait(&_isEmpty, &mtx);
    }

    void ConsumerWait()
    {
        pthread_cond_wait(&_isFull, &mtx);
    }

    void WakeupConsumer()
    {
        pthread_cond_signal(&_isFull);
    }

    void WakeupProducter()
    {
        pthread_cond_signal(&_isEmpty);
    }

    BlockQueue(int cap = CAP_INIT)
        : _cap(cap)
    {
        // 初始化锁，条件变量
        pthread_mutex_init(&mtx, nullptr);
        pthread_cond_init(&_isFull, nullptr);
        pthread_cond_init(&_isEmpty, nullptr);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&mtx);
        pthread_cond_destroy(&_isFull);
        pthread_cond_destroy(&_isEmpty);
    }

private:
    int _cap; // 阻塞队列容量
    queue<T> _bq;
    pthread_mutex_t mtx; // 锁
    pthread_cond_t _isFull;
    pthread_cond_t _isEmpty;
};